﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace Affinity.Synthesis
{
    internal class LambdaBindingTarget
    {
        #region Construction

        public LambdaBindingTarget(object target)
        {
            _target = new WeakReference(target);
        }

        #endregion


        #region Public Methods

        public TReturn BindLambda<TReturn>(string propertyName, Expression<Func<TReturn>> lambdaExpression, TReturn defaultValue)
        {            
            LambdaBinding lambdaBinding;

            lock (_lambdaBindingsLock)
            {
                var lambdaBindings = LambdaBindings;

                if (!lambdaBindings.TryGetValue(propertyName, out lambdaBinding))
                {
                    lambdaBinding = CreateLambdaBinding(lambdaExpression);
                    lambdaBindings[propertyName] = lambdaBinding;
                }
            }

            return (TReturn)lambdaBinding.BindUntyped(_target.Target, propertyName, defaultValue);
        }

        #endregion


        #region Private Implementation

        private Dictionary<string, LambdaBinding> LambdaBindings
        {
            get 
            { 
                Dictionary<string, LambdaBinding> lambdaBindings;
                var type = _target.Target.GetType();

                if (!_lambdaBindings.TryGetValue(type, out lambdaBindings))
                {
                    lambdaBindings = new Dictionary<string, LambdaBinding>();
                    _lambdaBindings[type] = lambdaBindings;
                }
                
                return lambdaBindings;
            }
        }

        private LambdaBinding<TReturn> CreateLambdaBinding<TReturn>(Expression<Func<TReturn>> lambdaExpression)
        {
            if (_InstanceRemovalVisitor == null) _InstanceRemovalVisitor = new InstanceRemovalVisitor(_target.Target);
            var staticLambda = _InstanceRemovalVisitor.MakeStaticLambda(lambdaExpression);
            return LambdaBinding.Create<TReturn>(staticLambda);
        }

        #endregion


        #region Fields

        private InstanceRemovalVisitor _InstanceRemovalVisitor;
        //TODO: Move into LambdaBindingTargetManager?
        private static readonly Dictionary<Type, Dictionary<string, LambdaBinding>> _lambdaBindings = new Dictionary<Type, Dictionary<string, LambdaBinding>>();  
        private static readonly object _lambdaBindingsLock = new object();
        private readonly WeakReference _target;

        #endregion


        #region Nested Classes

        private class InstanceRemovalVisitor : ExpressionVisitor
        {
            public InstanceRemovalVisitor(object instance)
            {
                _Instance = instance;
                _InstanceParameter = Expression.Parameter(instance.GetType());
            }

            public LambdaExpression MakeStaticLambda<TReturn>(Expression<Func<TReturn>> instanceLambda)
            {
                return Expression.Lambda(Visit(instanceLambda.Body), _InstanceParameter);
            }

            protected override Expression VisitConstant(ConstantExpression node)
            {
                if (node.Value == _Instance) return _InstanceParameter;
                return base.VisitConstant(node);
            }

            private readonly object _Instance;
            private readonly ParameterExpression _InstanceParameter;
        }

        #endregion
    }
}
